
/* GCSx
** TILESET.CPP
**
** Tileset support, objects and drawing
** Non-EDITOR members
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

const TileSet* TileSet::activeGenTexture = NULL;

TileSet::TileSet(class World* myWorld, int myId) : name(blankString), nameL(blankString) { start_func
    if (myId) assert(myWorld);
    
    id = myId;
    cached = 0;
    lockCount = 0;
    lockCountPlay = 0;
    cacheFile = NULL;
    texture = NULL;
    numTiles = 0;
    numTilesPerLine = TILE_DEFAULT_PERLINE;
    tiles = NULL;
    tsPerLine = 0;
    isFont = 0;
    defaultTransparent = 0;
    fontWidths = NULL;
    numCollisionMaps = 0;
    collisionMaps = NULL;
    world = myWorld;
    width = height = TILE_DEFAULT_SIZE;
    calculateCmSizes();
}

TileSet::~TileSet() { start_func
    if (tiles) SDL_FreeSurface(tiles);
    delete[] fontWidths;
    delete[] collisionMaps;
    delete cacheFile;
    delete texture;
    
    clipboardClearTileset(this);
}

void TileSet::calculateCmSizes() {
    cmHeight = height;
    cmWidth = width >> 5;
    if (width & 31) ++cmWidth;
}

void TileSet::cacheLoadTiles() throw_File { start_func
    // load tiles only
    try {
        assert(cacheFile);
        assert(cacheFile->getVersion() <= 1);
        assert(!tiles);

        cacheFile->rewind();

        if (numTiles) {
            int surfaceW = cacheFile->readInt();
            int surfaceH = cacheFile->readInt();
            int surfaceSize = cacheFile->readInt();

            if ((surfaceW != tsPerLine * width) ||
                (surfaceH != (numTiles + tsPerLine) / tsPerLine * height) ||
                (surfaceSize != surfaceW * surfaceH * 4)) {
                throw FileException("Corrupted tileset content");
            }

            tiles = createSurface32(surfaceW, surfaceH);

            // If pitch of created surface is "incorrect", we need to do extra work
            if (tiles->pitch == surfaceW * 4) {
                cacheFile->read(tiles->pixels, surfaceSize);
            }
            else {
                Uint8* dest = (Uint8*)tiles->pixels;
                for (int row = surfaceH; row > 0; --row) {
                    cacheFile->read(dest, surfaceW * 4);
                    dest += tiles->pitch;
                }
            }
        }
    }
    catch (...) {
        if (tiles) SDL_FreeSurface(tiles);
        tiles = NULL;
        throw;
    }
}
    
void TileSet::cacheLoad() throw_File { start_func
    if (cached) {
        try {
            // load into memory
            assert(!fontWidths);
            assert(!collisionMaps);

            // Rewinds for us
            cacheLoadTiles();

            if (numTiles) {
                if (isFont) {
                    fontWidths = new Uint32[numTiles + 1];
                    fontWidths[0] = 0; // 0 tile is assumed blank
                    cacheFile->readIntBulk(fontWidths + 1, numTiles);

                    // All sizes must be valid
                    for (int pos = 1; pos <= numTiles; ++pos) {
                        if (fontWidths[pos] > (Uint32)width) {
                            throw FileException("Corrupted font width content");
                        }
                    }
                }
            }

            if (numCollisionMaps) {
                int size = (numCollisionMaps + 1) * cmWidth * cmHeight;
                collisionMaps = new Uint32[size];
                cacheFile->readIntBulk(collisionMaps, size);
            }

            // We retain this pointer and reuse it IF we
            // unlock to 0 AND aren't modified
            cached = 0;
        }
        catch (...) {
            if (tiles) SDL_FreeSurface(tiles);
            tiles = NULL;
            delete[] collisionMaps;
            collisionMaps = NULL;
            delete[] fontWidths;
            fontWidths = NULL;
            throw;
        }
    }
}

int TileSet::getGlyphWidth(int tile) const { start_func
    assert(isFont);
    assert(fontWidths);
    assert(tile >= 0);
    assert(tile <= numTiles);
    
    return fontWidths[tile];
}

void TileSet::loadHeader(FileRead* file) throw_File { start_func
    assert(world);
    
    if (file->getVersion() > 1) {
        throw FileException("Unsupported tileset version %d", file->getVersion());
    }

    file->readStr(name);
    nameL = name;
    toLower(nameL);
    width = file->readInt();
    height = file->readInt();
    calculateCmSizes();
    numTiles = file->readInt();
    numCollisionMaps = file->readInt();
    numTilesPerLine = file->readInt();
    tsPerLine = file->readInt();
    defaultTransparent = file->readInt();
    isFont = file->readInt();
    id = file->readInt();
    
    // Check validity
    if ((width < MIN_TILESIZE) || (height < MIN_TILESIZE) ||
        (width > MAX_TILESIZE) || (height > MAX_TILESIZE) ||
        (numTiles < 0) || (numTiles > MAX_TILES) ||
        (numTilesPerLine < 0) || (numTilesPerLine > MAX_TILES) ||
        (tsPerLine < 0) || (tsPerLine > numTiles) ||
        ((numCollisionMaps) && (isFont)) ||
        (!id)) {
        throw FileException("Corrupted tileset header");
    }
}

void TileSet::loadContent(FileRead* file) { start_func
    assert(world);
    
    delete cacheFile;
    cacheFile = file;
    cached = 1;
}

int TileSet::isContentCached() const { start_func
    return cached;
}

int TileSet::doLock(int play) throw_File { start_func
    // Load everything if this is our first unlock at all
    if ((lockCount == 0) && (lockCountPlay == 0))
        cacheLoad();
    // Otherwise- first play lock- everything is ok
    // First non-play lock- we need to reload tiles back in
    else if ((lockCount == 0) && (!play))
        cacheLoadTiles();
        
    if (play) {
        ++lockCountPlay;
        if (!texture) {
            activeGenTexture = this;
            texture = new TextureMap(numTiles, width, height, genTexture);
            activeGenTexture = NULL;
        }
        if ((!lockCount) && (tiles)) {
            SDL_FreeSurface(tiles);
            tiles = NULL;
        }
    }
    else {
        ++lockCount;
    }
    return lockCount + lockCountPlay;
}

int TileSet::doUnlock(int play) { start_func
    if (play) --lockCountPlay;
    else --lockCount;

    assert(lockCount >= 0);
    assert(lockCountPlay >= 0);

    if (cacheFile) {
        if ((lockCount == 0) && (tiles)) {
            SDL_FreeSurface(tiles);
            tiles = NULL;
        }
        if ((lockCountPlay == 0) && (texture)) {
            delete texture;
            texture = NULL;
        }
        if ((lockCountPlay == 0) && (lockCount == 0)) {
            // Fully recache ourselves
            cached = 1;
            delete[] fontWidths;
            fontWidths = NULL;
        }
    }
    return lockCount + lockCountPlay;
}

int TileSet::isLocked() const { start_func
    return lockCount;
}

int TileSet::markLock() throw_File { start_func
    return doLock(0);
}

int TileSet::markUnlock() { start_func
    return doUnlock(0);
}

int TileSet::markLockPlay() throw_File { start_func
    return doLock(1);
}

int TileSet::markUnlockPlay() { start_func
    return doUnlock(1);
}

const SDL_Surface* TileSet::getTileSurface(int tile, int& x, int& y) const { start_func
    assert(tiles);
    assert(!cached);
    
    if (tile < 1) return NULL;
    if (tile > numTiles) return NULL;
    
    x = tile % tsPerLine * width;
    y = tile / tsPerLine * height;
    return tiles;
}

const TextureMap* TileSet::getTexture() { start_func
    return texture;
}

void TileSet::genTexture(int position, const SDL_Surface*& src, int& x, int& y) { start_func
    assert(!activeGenTexture->cached);
    assert(position > 0);
    assert(position <= activeGenTexture->numTiles);
    assert(activeGenTexture->tiles);
    
    src = activeGenTexture->tiles;
    x = position % activeGenTexture->tsPerLine * activeGenTexture->width;
    y = position / activeGenTexture->tsPerLine * activeGenTexture->height;
}
